﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.Xml;
using System.Threading;
using System.Diagnostics;
using Microsoft.Win32;
using Microsoft.TeamFoundation.Build.Client;
using System.IO;

namespace TeamDeploy.Activities
{
    [BuildActivity(HostEnvironmentOption.Agent)]
    public sealed class Deploy : CustomActivityBase
    {
        private static XmlDocument deployDocument;
        private static object workerLocker = new object();
        private static int runningWorkers;
        private static string machineDeployedFailed;

        // Define an activity input argument of type string
        [RequiredArgument]
        public InArgument<string> DeployScript { get; set; }
        
        [RequiredArgument]
        public InArgument<string> KillAppPathFilename { get; set; }
        
        [RequiredArgument]
        public InArgument<string> RemoteExecutePathFilename { get; set; }

        
        // If your activity returns a value, derive from CodeActivity<TResult>
        // and return the value from the Execute method.
        protected override void Execute(CodeActivityContext context)
        {
            // Obtain the runtime value of the Text input argument
            string deployScript = context.GetValue(this.DeployScript);
            string killAppPathFilename = context.GetValue(this.KillAppPathFilename);
            string remoteExeutePathFilename = context.GetValue(this.RemoteExecutePathFilename);
            bool ignoreExitCode = false;
            bool.TryParse(context.GetValue(this.IgnoreExitCode), out ignoreExitCode);
       
            try
            {

                // InitializeBuildStep("Deploying build to target machines", "Running deploy to target machines");
                LogMessage(context,"Start Deploy");
                machineDeployedFailed = string.Empty;
                deployDocument = new XmlDocument();
                deployDocument.Load(deployScript);

                XmlNodeList targetMachinesNodes;

                //Get a list of all target machines 
                targetMachinesNodes = deployDocument.SelectNodes("DeploymentScript/TargetMachines/TargetMachine");

                if (targetMachinesNodes == null)
                {

                    throw new ArgumentException("No target machines were specified in the build script.");
                }

                runningWorkers = targetMachinesNodes.Count;

                for (int targetMachineLoop = 0; targetMachineLoop < runningWorkers; targetMachineLoop++)
                {
                    
                    ThreadPoolHelper.QueueUserWorkItem(delegate { DeployToTargetMachine(targetMachineLoop, remoteExeutePathFilename, killAppPathFilename, context); });
                }

                lock (workerLocker)
                {
                    while (runningWorkers > 0) Monitor.Wait(workerLocker);
                }

                if (machineDeployedFailed.Length > 0)
                {
                    throw new Exception("One of the machines failed to deploy. Reason: " + machineDeployedFailed);
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        private void DeployToTargetMachine(int instance, string remoteExecutePathFilename, string killAppPathFilename, CodeActivityContext context)
        {
            string targetMachine = "";
            string trackingStep = "";

            string serviceName;
            StopService stopTask;
            StartService startTask;
            try
            {
                XmlNodeList targetMachinesNodes;

                //Get a list of all target machines 
                targetMachinesNodes = deployDocument.SelectNodes("DeploymentScript/TargetMachines/TargetMachine");


                XmlNode targetMachineNode = targetMachinesNodes[(int)instance - 1];

                XmlNodeList nodeList;
                XmlNodeList msiList;
                XmlNodeList webDeployList;
                XmlNodeList xcopyList;

                string nodeValue;
                if (targetMachineNode == null)
                    return;

                targetMachine = targetMachineNode.Attributes["name"].Value;
                LogMessage(context, "Deploying to TargetMachine: " + targetMachine);
                //Cleanup PsExec if the node is specified
                nodeList = deployDocument.SelectNodes("DeploymentScript/DeploymentSteps/CleanupPsExec");
                if (nodeList != null)
                {
                    CleanupPsExec cleanup = new CleanupPsExec();
                    cleanup.TargetMachine = targetMachine;
                    cleanup.KillAppPathFilename = killAppPathFilename;
                    //var workflowInvoker = new WorkflowInvoker(cleanup);
                    AutoResetEvent waitHandle = new AutoResetEvent(false); 
                    WorkflowApplication workflowApplication = new WorkflowApplication(cleanup);
                    workflowApplication.Completed = delegate(
                        WorkflowApplicationCompletedEventArgs e)
                    {

                        if (e.TerminationException != null)
                        {

                            LogMessage(context, e.TerminationException.Message);

                        }


                        waitHandle.Set();

                    }; 

                    workflowApplication.Run();
                    waitHandle.WaitOne();

                }

                //Get a list of all process names to be killed (if any are specified) 
                nodeList = deployDocument.SelectNodes("DeploymentScript/DeploymentSteps/KillProcess");

                if ((nodeList != null))
                {
                    foreach (XmlNode KillProcess in nodeList)
                    {
                        nodeValue = KillProcess.Attributes["name"].Value;
                        LogMessage(context, "Killing Process " + nodeValue + " on " + targetMachine);

                        KillProcess kill = new KillProcess();
                        kill.TargetMachine = targetMachine;
                        kill.KillAppPathFilename = killAppPathFilename;
                        kill.ProcessName = nodeValue;
                        kill.IgnoreExitCode = "true";
                        AutoResetEvent waitHandle = new AutoResetEvent(false); 
                        WorkflowApplication workflowApplication = new WorkflowApplication(kill);
                        workflowApplication.Completed = delegate(
                            WorkflowApplicationCompletedEventArgs e)
                        {

                            if (e.TerminationException != null)
                            {

                                LogMessage(context, e.TerminationException.Message);

                            }


                            waitHandle.Set();

                        }; 



                        workflowApplication.Run();
                        waitHandle.WaitOne();
                    }
                }

                msiList = deployDocument.SelectNodes("DeploymentScript/DeploymentSteps/MSI");

                foreach (XmlNode MSI in msiList)
                {
                    string uninstallProductCode;
                    string uninstallProductName;
                    string msiPathFile;
                    string overrideDefaultArgs;
                    string extraArgs;

                    uninstallProductCode = GetNodeValue(MSI, "UninstallProductCode");
                    msiPathFile = MSI["MSIPathFile"].InnerText;
                    overrideDefaultArgs = GetNodeValue(MSI, "OverrideDefaultArgs");
                    extraArgs = GetNodeValue(MSI, "ExtraArgs");
                    serviceName = GetNodeValue(MSI, "ServiceName");
                    uninstallProductName = GetNodeValue(MSI, "UninstallProductName");

                    //If this MSI package is for a Windows service then stop the service 
                    if ((!string.IsNullOrEmpty(serviceName)))
                    {
                        stopTask = new StopService();
                        stopTask.TargetMachine = targetMachine;
                        stopTask.ServiceName = serviceName;
                        trackingStep = "Stopping service: " + serviceName + " on " + targetMachine;
                        LogMessage(context, trackingStep);
                        stopTask.IgnoreExitCode = "true";
                        AutoResetEvent waitHandle = new AutoResetEvent(false);
                        WorkflowApplication workflowApplication = new WorkflowApplication(stopTask);
                        workflowApplication.Completed = delegate(
                WorkflowApplicationCompletedEventArgs e)
                        {

                            if (e.TerminationException != null)
                            {

                                LogMessage(context, e.TerminationException.Message);

                            }


                            waitHandle.Set();

                        }; 



                        workflowApplication.Run();
                        waitHandle.WaitOne();
                    }

                    //If an uninstall product code was specified, then uninstall the MSI before proceeding 
                    if ((!string.IsNullOrEmpty(uninstallProductCode) && IsProductInstalled(uninstallProductCode, targetMachine)))
                    {
                        trackingStep = "Uninstalling " + uninstallProductCode + " usinging remoteExecute " +
                                       remoteExecutePathFilename;
                        LogMessage(context, trackingStep);
                        Uninstall uninstallTask = new Uninstall();
                        uninstallTask.ProductCode = uninstallProductCode;
                        uninstallTask.RemoteExecutePathFilename = remoteExecutePathFilename;
                        uninstallTask.TargetMachine = targetMachine;
                        uninstallTask.IgnoreExitCode = "true";
                        AutoResetEvent waitHandle = new AutoResetEvent(false);
                        WorkflowApplication workflowApplication = new WorkflowApplication(uninstallTask);
                        workflowApplication.Completed = delegate(
                            WorkflowApplicationCompletedEventArgs e)
                        {

                            if (e.TerminationException != null)
                            {

                                Console.WriteLine(e.TerminationException.Message);

                            }


                            waitHandle.Set();

                        }; 



                        workflowApplication.Run();
                        waitHandle.WaitOne();
                    }

                    //If an uninstlal product name was specified, then uninstall all applications that 
                    //start with the uninstall product name 
                    if ((!string.IsNullOrEmpty(uninstallProductName)))
                    {
                        List<string> uninstallList = GetUninstallProductCodes(uninstallProductName, targetMachine);

                        //Uninstall each product code matching the UninstallProductName 
                        if (uninstallList != null && uninstallList.Count > 0)
                        {
                            foreach (string code in uninstallList)
                            {
                                trackingStep = "Uninstalling " + code + " on " + targetMachine;
                                LogMessage(context, trackingStep);
                                Uninstall uninstallTask = new Uninstall();
                                uninstallTask.ProductCode = code;
                                uninstallTask.RemoteExecutePathFilename = remoteExecutePathFilename;
                                uninstallTask.TargetMachine = targetMachine;
                                AutoResetEvent waitHandle = new AutoResetEvent(false);
                                WorkflowApplication workflowApplication = new WorkflowApplication(uninstallTask);
                                workflowApplication.Completed = delegate(
                                    WorkflowApplicationCompletedEventArgs e)
                                {

                                    if (e.TerminationException != null)
                                    {

                                        LogMessage(context, e.TerminationException.Message);

                                    }


                                    waitHandle.Set();

                                }; 



                                workflowApplication.Run();
                                waitHandle.WaitOne();
                            }
                        }
                    }


                    //Get a list of all files to be deleted (if any) 
                    nodeList = deployDocument.SelectNodes("DeploymentScript/DeploymentSteps/DeleteFile");

                    if ((nodeList != null))
                    {
                        foreach (XmlNode fileNode in nodeList)
                        {
                            nodeValue = fileNode.Attributes["path"].Value;

                            trackingStep = "DeleteFiles " + nodeValue + " on " + targetMachine;
                            LogMessage(context, trackingStep);
                            DeleteFiles(targetMachine, nodeValue, remoteExecutePathFilename, context);
                        }
                    }

                    //Get a list of all folders (and contained files) to be deleted (if any) 
                    nodeList = deployDocument.SelectNodes("DeploymentScript/DeploymentSteps/DeleteFolder");

                    if ((nodeList != null))
                    {
                        foreach (XmlNode fileNode in nodeList)
                        {
                            nodeValue = fileNode.Attributes["path"].Value;

                            trackingStep = "Deleting Folder " + nodeValue + " on " + targetMachine;
                            LogMessage(context, trackingStep);
                            DeleteFolder(targetMachine, nodeValue, remoteExecutePathFilename, context);
                        }
                    }


                    trackingStep = "Installing MSI " + msiPathFile + " on " + targetMachine;
                    LogMessage(context, trackingStep);
                    //Install the MSI package 
                    Install installTask = new Install();
                    installTask.RemoteExecutePathFilename = remoteExecutePathFilename;
                    installTask.MSIPathFilename = msiPathFile;
                    installTask.ExtraArgs = extraArgs;
                    installTask.OverrideDefaultArgs = overrideDefaultArgs;
                    installTask.TargetMachine = targetMachine;
                    AutoResetEvent installTaskWaitHandle = new AutoResetEvent(false);
                    WorkflowApplication installTaskWorkflow = new WorkflowApplication(installTask);
                    installTaskWorkflow.Completed = delegate(
                        WorkflowApplicationCompletedEventArgs e)
                    {

                        if (e.TerminationException != null)
                        {

                            LogMessage(context, e.TerminationException.Message);

                        }


                        installTaskWaitHandle.Set();

                    }; 



                    installTaskWorkflow.Run();
                    installTaskWaitHandle.WaitOne();

                    //If this MSI package is for a Windows service then restart the service 
                    if ((serviceName.Length > 0))
                    {
                        trackingStep = "Starting Service " + serviceName + " on " + targetMachine;
                        LogMessage(context, trackingStep);
                        startTask = new StartService();

                        startTask.TargetMachine = targetMachine;
                        startTask.ServiceName = serviceName;
                        AutoResetEvent waitHandle = new AutoResetEvent(false);
                        WorkflowApplication workflowApplication = new WorkflowApplication(startTask);
                        workflowApplication.Completed = delegate(
                            WorkflowApplicationCompletedEventArgs e)
                        {

                            if (e.TerminationException != null)
                            {

                                LogMessage(context, e.TerminationException.Message);

                            }


                            waitHandle.Set();

                        }; 

                        workflowApplication.Run();
                        waitHandle.WaitOne();
                    }
                }

                webDeployList = deployDocument.SelectNodes("DeploymentScript/DeploymentSteps/WebDeploy");

                foreach (XmlNode webDeploy in webDeployList)
                {
                    string deploymentPackageSharePathCmd = webDeploy.Attributes["deploymentPackageSharePathCmd"].Value;
                    string username = "";
                    string password = "";
                    string allowUntrusted = "";
                    string useBasicAuthentication = "";

                    if (webDeploy.Attributes["username"] != null)
                    {
                        username = webDeploy.Attributes["username"].Value;
                    }
                    if (webDeploy.Attributes["password"] != null)
                    {
                        password = webDeploy.Attributes["password"].Value;
                    }
                    if (webDeploy.Attributes["useBasicAuthentication"] != null)
                    {
                        useBasicAuthentication = webDeploy.Attributes["useBasicAuthentication"].Value;
                    }
                    if (webDeploy.Attributes["allowUntrusted"] != null)
                    {
                        allowUntrusted = webDeploy.Attributes["allowUntrusted"].Value;
                    }                   

                    

                    WebDeploy webDeployTask = new WebDeploy();

                    webDeployTask.TargetMachine = targetMachine;
                    webDeployTask.DeploymentPackageSharePathCmd = deploymentPackageSharePathCmd;
                    webDeployTask.Username = username;
                    webDeployTask.Password = password;
                    webDeployTask.UseBasicAuthentication = useBasicAuthentication;
                    webDeployTask.AllowUntrusted = allowUntrusted;

                    AutoResetEvent waitHandle = new AutoResetEvent(false);
                    WorkflowApplication workflowApplication = new WorkflowApplication(webDeployTask);
                    workflowApplication.Completed = delegate(
                        WorkflowApplicationCompletedEventArgs e)
                    {

                        if (e.TerminationException != null)
                        {

                            LogMessage(context, e.TerminationException.Message);

                        }


                        waitHandle.Set();

                    };

                    workflowApplication.Run();
                    waitHandle.WaitOne();


                }

                xcopyList = deployDocument.SelectNodes("DeploymentScript/DeploymentSteps/XCopy");

                foreach (XmlNode xCopyNode in xcopyList)
                {
                    string sourceSharePath = xCopyNode.Attributes["sourceSharePath"].Value;
                    string destinationLocalPath = xCopyNode.Attributes["destinationLocalPath"].Value;
                    string excludeSubdirectories = "";
                    string overwriteReadOnlyFiles = "";
                    string extraArgs = "";

                    if (xCopyNode.Attributes["excludeSubdirectories"] != null)
                    {
                        excludeSubdirectories = xCopyNode.Attributes["excludeSubdirectories"].Value;
                    }
                    if (xCopyNode.Attributes["overwriteReadOnlyFiles"] != null)
                    {
                        overwriteReadOnlyFiles = xCopyNode.Attributes["overwriteReadOnlyFiles"].Value;
                    }
                    if (xCopyNode.Attributes["extraArgs"] != null)
                    {
                        extraArgs = xCopyNode.Attributes["extraArgs"].Value;
                    }

                    XCopy xcopyTask = new XCopy();

                    xcopyTask.TargetMachine = targetMachine;
                    xcopyTask.SourceSharePath = sourceSharePath;
                    xcopyTask.DestinationLocalPath = destinationLocalPath;
                    xcopyTask.ExcludeSubdirectories = excludeSubdirectories;
                    xcopyTask.OverwriteReadOnlyFiles = overwriteReadOnlyFiles;
                    xcopyTask.RemoteExecutePathFilename = remoteExecutePathFilename;
                    xcopyTask.ExtraArgs = extraArgs;

                    AutoResetEvent waitHandle = new AutoResetEvent(false);
                    WorkflowApplication workflowApplication = new WorkflowApplication(xcopyTask);
                    workflowApplication.Completed = delegate(
                        WorkflowApplicationCompletedEventArgs e)
                    {

                        if (e.TerminationException != null)
                        {

                            LogMessage(context, e.TerminationException.Message);

                        }


                        waitHandle.Set();

                    };

                    workflowApplication.Run();
                    waitHandle.WaitOne();


                }

            }

            catch (Exception ex)
            {
                machineDeployedFailed = ex.ToString() + "::" + ex.StackTrace;
                LogMessage(context, machineDeployedFailed);
            }
            finally
            {
                lock (workerLocker)
                {
                    runningWorkers--; Monitor.Pulse(workerLocker);
                }
            }
        }


        private static string GetNodeValue(XmlNode nodeList, string nodeName)
        {
            XmlNode searchNode;

            searchNode = nodeList.SelectSingleNode(nodeName);

            if ((searchNode != null))
            {
                //The "ServiceName" tag was found so let's obtain the value 
                return searchNode.InnerText.Trim();
            }
            else
            {
                //If the "ServiceName" tag is not present then we assume it is not a service 
                return string.Empty;
            }
        }


        private void DeleteFiles(string machineName, string path, string remoteExecutePathFilename, CodeActivityContext context)
        {
            try
            {
                string processArgs = "\\\\" + machineName + " -accepteula CMD /C DEL /Q \"" + path + "\"";

                using (System.Diagnostics.Process process = new System.Diagnostics.Process())
                {
                    process.StartInfo.FileName = remoteExecutePathFilename;
                    //process.StartInfo.WorkingDirectory = SearchPathRoot.Get(context);                    
                    process.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
                    process.StartInfo.UseShellExecute = false;
                    process.StartInfo.ErrorDialog = false;
                    process.StartInfo.CreateNoWindow = true;
                    process.StartInfo.RedirectStandardOutput = true;
                    process.StartInfo.Arguments = processArgs;

                    try
                    {
                        process.Start();
                        string output = process.StandardOutput.ReadToEnd();
                        process.WaitForExit();
                        LogMessage(context, "Delete Files Output:" + output);
                    }
                    catch (Exception ex)
                    {
                        LogMessage(context,ex.ToString());
                        throw ex;
                    }

                    //Process p = Process.Start(remoteExecutePathFilename, ProcessArgs);
                    //p.WaitForExit();

                }

            }
            catch (Exception)
            {
                throw;
            }
        }

        private void DeleteFolder(string machineName, string path, string remoteExecutePathFilename, CodeActivityContext context)
        {
            try
            {
                string processArgs = "\\\\" + machineName + " -accepteula CMD /C RMDIR /S /Q \"" + path + "\"";

               // Log.LogMessage("Delete Folder: " + path);
                using (System.Diagnostics.Process process = new System.Diagnostics.Process())
                {
                    process.StartInfo.FileName = remoteExecutePathFilename;
                    //process.StartInfo.WorkingDirectory = SearchPathRoot.Get(context);                    
                    process.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
                    process.StartInfo.UseShellExecute = false;
                    process.StartInfo.ErrorDialog = false;
                    process.StartInfo.CreateNoWindow = true;
                    process.StartInfo.RedirectStandardOutput = true;
                    process.StartInfo.Arguments = processArgs;

                    try
                    {
                        process.Start();
                        string output = process.StandardOutput.ReadToEnd();
                        process.WaitForExit();
                        LogMessage(context,"Delete Fold Output:" + output);
                    }
                    catch (Exception ex)
                    {
                        LogMessage(context, ex.ToString());
                        throw ex;
                    }


                    //Process.Start(remoteExecutePathFilename, processArgs);
                }
            }
            catch (Exception)
            {
               // Log.LogMessage(string.Format("Could not delete folder'{0}' on machine {1}.", path, machineName));
                throw;
            }

        }
        public List<string> GetUninstallProductCodes(string uninstallProductName, string targetMachine)
        {
            try
            {
                uninstallProductName = uninstallProductName.Trim().ToLower();
                List<string> codes = new List<string>();
                RegistryKey regKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, targetMachine);
                RegistryKey tempKey;

                regKey = regKey.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", false);

                foreach (string subKey in regKey.GetSubKeyNames())
                {
                    tempKey = regKey.OpenSubKey(subKey, false);

                    if (Array.IndexOf(tempKey.GetValueNames(), "DisplayName") >= 0)
                    {
                        if (tempKey.GetValue("DisplayName").ToString().ToLower()
                            .StartsWith(uninstallProductName))
                        {
                            codes.Add(subKey);
                        }
                    }
                }

                regKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, targetMachine);

                regKey = regKey.OpenSubKey(@"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall", false);

                if (regKey != null)
                {
                    foreach (string subKey in regKey.GetSubKeyNames())
                    {
                        tempKey = regKey.OpenSubKey(subKey, false);

                        if (Array.IndexOf(tempKey.GetValueNames(), "DisplayName") >= 0)
                        {
                            if (tempKey.GetValue("DisplayName").ToString().ToLower()
                                .StartsWith(uninstallProductName))
                            {
                                codes.Add(subKey);
                            }
                        }
                    }
                }

                return codes;
            }
            catch (Exception)
            {
               // Log.LogMessage(string.Format("Could not Get Uninstall Product Codes for Name '{0}' on machine {1}. Exception: {2}", uninstallProductName, targetMachine, ex.ToString()));
                throw;
            }
        }


        public bool IsProductInstalled(string uninstallProductCode, string targetMachine)
        {
            try
            {            
                List<string> codes = new List<string>();
                RegistryKey regKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, targetMachine);

                regKey = regKey.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" + uninstallProductCode, false);

                if (regKey != null)
                    return true;
                else
                    return false;
            }
            catch (Exception)
            {
                // Log.LogMessage(string.Format("Could not Get Uninstall Product Codes for Name '{0}' on machine {1}. Exception: {2}", uninstallProductName, targetMachine, ex.ToString()));
                throw;
            }
        }

 

    }
}
